package com.zlyx.easy.core.factory;

import java.lang.invoke.MethodHandles;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

import org.springframework.beans.factory.FactoryBean;

import com.zlyx.easy.core.factory.interfaces.FactoryBeanDefiner;
import com.zlyx.easy.core.spring.SpringUtils;
import com.zlyx.easy.core.utils.ClassUtils;
import com.zlyx.easy.core.utils.MethodUtils;
import com.zlyx.easy.core.utils.StringUtils;

/**
 * @author 赵光
 * @Desc
 * @Date 2018年12月14日 下午3:34:09
 */
public class DefaultFactoryBean<T> implements InvocationHandler, FactoryBean<T> {

	private Class<?> factoryBeanHandlerClass;

	protected Class<T> factoryInterface;

	private boolean addToConfig = true;

	public DefaultFactoryBean(Class<T> factoryInterface) {
		this.factoryInterface = factoryInterface;
	}

	public DefaultFactoryBean() {
	}

	@Override
	public T getObject() throws Exception {
		if (factoryInterface.isInterface()) {
			return ClassUtils.getInstance(factoryInterface, DefaultFactoryBean.this);
		}
		return null;
	}

	@Override
	public Class<?> getObjectType() {
		return factoryInterface;
	}

	public void setTrunkInterface(Class<T> factoryInterface) {
		this.factoryInterface = factoryInterface;
	}

	public Class<T> getTrunkInterface() {
		return factoryInterface;
	}

	public void setAddToConfig(boolean addToConfig) {
		this.addToConfig = addToConfig;
	}

	public boolean isAddToConfig() {
		return addToConfig;
	}

	@Override
	public boolean isSingleton() {
		return true;
	}

	protected Object invokeDefaultMethod(Object proxy, Method method, Object[] args) throws Throwable {
		final Constructor<MethodHandles.Lookup> constructor = MethodHandles.Lookup.class
				.getDeclaredConstructor(Class.class, int.class);
		if (!constructor.isAccessible()) {
			constructor.setAccessible(true);
		}
		final Class<?> declaringClass = method.getDeclaringClass();
		return constructor
				.newInstance(declaringClass,
						MethodHandles.Lookup.PRIVATE | MethodHandles.Lookup.PROTECTED | MethodHandles.Lookup.PACKAGE
								| MethodHandles.Lookup.PUBLIC)
				.unreflectSpecial(method, declaringClass).bindTo(proxy).invokeWithArguments(args);
	}

	protected boolean isDefaultMethod(Method method) {
		return (method.getModifiers() & (Modifier.ABSTRACT | Modifier.PUBLIC | Modifier.STATIC)) == Modifier.PUBLIC
				&& method.getDeclaringClass().isInterface();
	}

	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		if (Object.class.equals(method.getDeclaringClass())) {
			return method.invoke(this, args);
		} else if (MethodUtils.isDefaultMethod(method)) {
			return MethodUtils.invokeDefaultMethod(proxy, method, args);
		}
		FactoryBeanDefiner handler = null;
		if (SpringUtils.containsBean(factoryBeanHandlerClass.getName())) {
			handler = SpringUtils.getBean(factoryBeanHandlerClass.getName());
		}
		if (handler == null) {
			handler = SpringUtils.getBean(StringUtils.toLowCaseFirst(factoryBeanHandlerClass.getSimpleName()));
		}
		if (handler != null) {
			return handler.excute(factoryInterface, method, args);
		}
		return null;
	}

	public void setFactoryBeanHandler(Class<?> factoryBeanHandlerClass) {
		this.factoryBeanHandlerClass = factoryBeanHandlerClass;
	}
}
